package com.stemaini.core.stem;

import com.stemaini.core.database.Kamus;

/**
 * @author Putu Adhi Kerta Mahendra 5104 100 089
 * copyright(C) -  17 Juni 2008
 * Jurusan Teknik Informatika - ITS Surabaya
 */

public class Kata {
    private String label, // label dari kata bersangkutan
            bentukDasar;  // bentuk kata dasar dari label bersangkutan
    
    private Kamus kamus;  //representasi buffer kamus dari database
    
    //berikut ini adalah variabel-variabel log :
    private int rule, logRules;  //untuk mencatat rule + log-nya
    
    private boolean prefixSama ; //untuk mencatat apakah terjadi kesamaan prefix
    
    public final static int STANDARD_CS_STEMMER = 2;
    public final static int ENHANCED_CS_STEMMER = 3;
    private int pilihanStemmer;
    /**
     * Fungsi Kata(String label , Koneksi kon)
     * @param label representasi "label" untuk Kata bersangkutan
     * @param kon representasi kelas KONEKSI
     */
    public Kata(String label , Kamus kamus) {
        this.label = label.toLowerCase();
        this.kamus = kamus;
        this.bentukDasar = "";
    }

    /**
     * Fungsi getLabel()
     * berguna untuk mendapatkan label kelas KATA ini.
     * @return bentukDasar atribut "bentukDasar" sebagai "kata-dasar" 
     * dari label kelas ini.
     */
    public String getBentukDasar(int pilihanStemmer) {
        this.pilihanStemmer = pilihanStemmer;
        //disini kita stem dulu label Kata bersangkutan
        doStem();
        //terakhir berikan return value yakni bentuk dasar kata brsgkutan
        return bentukDasar;
    }

    /**
     * Fungsi getLabel()
     * berguna untuk mendapatkan label kelas KATA ini.
     * @return label atribut label kelas ini.
     */
    public String getLabel() {
        return label;
    }

    /**
     * Fungsi setLabel(String label)
     * berguna untuk men-set label dari kelas KATA ini.
     * @param label untuk men-set atribut label kelas ini.
     */
    public void setLabel(String label) {
        this.label = label;
    }

    /**
     * Fungsi doStem()
     * merupakan fungsi public yg dpt dipanggil untuk mendapatkan bentuk kata-
     * dasar dari objek KATA. Alur method ini dapat berubah, yakni dengan
     * menghilangkan akhiran terlebih dahulu-baru awalan , atau 
     * awalan dahulu-baru akhiran, sesuai RULE PRECEDENCE yg dinyatakan Jelita Asian.
     * Penanganan stemming untuk kata ulang, dilakukan terakhir, setelah 
     * penghilangan awalan-akhiran gagal.
     * Method ini akan men-set atribut "bentukDasar" dari kelas KATA ini sesuai 
     * hasil stemming yg diperoleh. 
     */
    public void doStem()
    {
        String kata = this.label; //menyalin kata awal yg akan di stem
        String logKata = kata;
        String logKataBerawalan;
        String partikel="", //akhiran bertipe PARTIKEL (-kah,-lah,-tah,-pun)
               kataGantiKepunyaan="", //kata ganti milik (-ku,-mu,-nya)
               DS=""; //Derivation Suffix: akhiran bertipe -i, -kan, atau -an.
        
        //Sebelumnya, kita cek dulu, apakah untuk fase pertama, reduksi dimulai
        //dari awalan dulu, atau reduksi akhiran dulu
        boolean isPrecedence = FungsiStem.cekPrecedence(kata);
        
        //Tahap 1. Cari ke kamus
        if(dictionaryLookup(kata))
        {
            this.bentukDasar = kata;
            return;
        }

        //Tahap 2. Cek Rule Precedence, jika OK berarti reduksi awalan dahulu
        if(isPrecedence){//lakukan reduksi awalan ini dulu
            //isRecoded kita set FALSE agar proses reduksi awalan ini tidak
            //langsung diikuti recoding...
            kata = reduksiAwalan(kata, DS, false); 
            
            if(dictionaryLookup(kata)){
                this.bentukDasar = kata; return;
            }
        }

        //reduksi akhiran:
        //Akhiran ada 3 tipe: Partikel, Kata Ganti Kepunyaan & Derivation Suffix
        partikel = FungsiStem.getPartikel(kata);
        kataGantiKepunyaan = FungsiStem.getKataGantiKepunyaan(kata.substring(0,
                kata.length()-partikel.length()) );
        DS = FungsiStem.getDS(kata.substring(0,
                kata.length()-partikel.length()-kataGantiKepunyaan.length()  ));
        
        //Tahap Berikutnya: Reduksi Akhiran
        kata = reduksiAkhiran(kata, partikel, kataGantiKepunyaan, DS);
        //kita catat juga di log
        logKataBerawalan = reduksiAkhiran(this.label,partikel, kataGantiKepunyaan, DS);
        if(dictionaryLookup(kata)){
            this.bentukDasar = kata; return;
        }       
        
        //Tahap di bawah ini dieksekusi jika isPrecedence = true
        //yakni, Reduksi Awalan + Reduksi Akhiran sudah dilewati. 
        if(isPrecedence)
        {
            if(prefixSama)
                kata = recoding(logRules,kata);
            else
                kata = recoding(rule,kata);
            
            if(dictionaryLookup(kata)){
                this.bentukDasar = kata; return;
            }    
        }
        
        //jika precedence false, harusnya reduksi akhiran di atas dulu,
        //baru reduksi awalan di bawah ini
        if(!isPrecedence){
            //isRecoded kita set TRUE, karena reduksi awalan + akhiran tahap 1
            //shrusnya sudah dilewati, jd reduksi Awalan langsung diikuti recoding.
            kata = reduksiAwalan(kata, DS , true); 
            if(dictionaryLookup(kata)){
                this.bentukDasar = kata; return;
            }
        }

        //di bawah ini adalah Optimisasi yg saya lakukan
        //Jika sampai tahap ini, Kata Dasar belum juga ditemukan, maka lakukan
        //Kombinasi pengembalian akhiran satu per satu + reduksiAwalan
        //input untuk fungsi loopPengembalianAkhiran adalah Kata, minus akhirannya
        if(pilihanStemmer==Kata.ENHANCED_CS_STEMMER){
            kata = loopPengembalianAkhiran(logKataBerawalan,partikel, 
                    kataGantiKepunyaan, DS);
            if(dictionaryLookup(kata))
            {
                this.bentukDasar = kata; return;
            }
        }
        
        
        //Selain itu dicek juga apakah apakah kata ini = kata ulang
        int indexStrip = logKata.indexOf('-');
            if(indexStrip!=-1 && indexStrip>0){//berarti ada tanda -
                Kata kiri = new Kata(logKata.substring(0,indexStrip) , this.kamus);
                Kata kanan = new Kata(logKata.substring(indexStrip+1,logKata.length()),
                                                            this.kamus);
                kiri.doStem();kanan.doStem();
                String stemKiri = kiri.getBentukDasar(this.pilihanStemmer);
                String stemKanan = kanan.getBentukDasar(this.pilihanStemmer);

                if(stemKiri.equals(stemKanan)
                        && !stemKiri.equals("") && !stemKanan.equals("")){
                //jika hasil stem kira = kanan & isinya != kosong berarti
                    this.bentukDasar = stemKiri; return;
                }
        }

        //TERAKHIR..apabila sampai disini masih belum menemukan kata dasarnya, 
        //maka bentuk awalnya dikembalikan sebagai kata dasar...FINISH!!!
        if(dictionaryLookup(kata))
        {
            this.bentukDasar = kata; return;
        }
        else
        {
            this.bentukDasar = this.label;
        }

    }
    
    /**
     * Fungsi loopReduksiAwalan(String kata, String DS)
     * merupakan method yg digunakan method utamanya, yakni reduksiAwalan()
     * jika dilihat dalam thesis Jelita Asian, method ini merupakan
     * implementasi STEP 4 dan STEP 5(=recoding) dari algoritma CS-Stemmer. 
     */
    private String loopReduksiAwalan(String kata, String DS, boolean isRecoded)
    {
        int count = 1 ;
        rule = 0 ; logRules=0;
        prefixSama = false;
        
        String prefix = "";
        String logPrefix = "";
        String word = kata;
       
        do{
            if(FungsiStem.cekKombinasiTerlarang(word,DS)){//cek kombinasi terlarang
                break;
            }

            rule = FungsiStem.getRule(word,this.pilihanStemmer); //cari Rule awalan yg akan dihilangkan
            prefix = FungsiStem.getPrefix(rule,word); //dapatkan string prefix sesuai rule-nya

            if(rule==0){
                rule = logRules; break;
            }

            if(prefix.equalsIgnoreCase(logPrefix)){//terjadi kesamaan awalan
                prefixSama = true;
                break;
            }
            else{
                word = FungsiStem.getReduksi(rule,word);
                if(dictionaryLookup(word)) return word;
            }
            logRules = rule;
            logPrefix = prefix; //dicatat ke log
            count++;
        }while(count<=3);

        if(!dictionaryLookup(word) && isRecoded){
            if(prefixSama)
                word = recoding(logRules,word);
            else
                word = recoding(rule,word);

        }

        return word;
    }

    /** 
     * Fungsi reduksiAwalan(String kata, String partikel,
            String kataGantiKepunyaan, String DS)
     * merupakan MASTER METHOD untuk mereduksi awalan-awalan dari suatu kata.
     * Parameter partikel, kataGantiKepunyaan, dan DS masuk sbg parameter method 
     * ini karena terkadang, diperlukan proses penggabungan kembali akhiran2 tsb
     * satu per satu, dan mereduksi-awalan ulang dari kata yang 
     * memperoleh akhirannya kembali tsb.
     * Metode ini merupakan pengembangan yang saya lakukan (Putu Adhi Kerta M.) 
     */
    private String reduksiAwalan(String kata, String DS , boolean isRecoded)
    {
        String hasil = new String(kata);
        
        //pertama lakukan reduksi dgn memanggil loopReduksiAwalan
        
        hasil = loopReduksiAwalan(hasil,DS,isRecoded);
        if(dictionaryLookup(hasil))
            return hasil;

        //step-step di bawah ini melakukan reduksi awalan
        //dengan akhiran yg telah dihilangkan, mulai dimasukkan satu per satu
        //mulai dari Derivation Suffixes DS = -i, -kan, -an
        return hasil;
    }

    /**
     * Fungsi reduksiAkhiran(String kata, String partikel,
            String kataGantiKepunyaan, String DS)
     * berguna untuk mendapatkan kata yg tereduksi akhirannya, sesuai pola
     * [kata-dasar] + DerivationSuffix + KataGantiKepunyaan + Partikel , 
     * sehingga nantinya akan tersisa [kata-dasar] saja. 
     */
    private String reduksiAkhiran(String kata, String partikel,
            String kataGantiKepunyaan, String DS)
    {
        //lakukan reduksi akhiran, dimulai dari partikel
        if(!partikel.equals("")){
            kata = kata.substring(0,kata.length()-partikel.length());
            if(dictionaryLookup(kata))
                return kata;
        }
        //lalu reduksi kata ganti kepunyaan
        if(!kataGantiKepunyaan.equals("")){
            kata = kata.substring(0,kata.length()-kataGantiKepunyaan.length());
            if(dictionaryLookup(kata))
                return kata;
        }
        //terakhir reduksi Derivation suffix (-i,-kan,-an)
        if(!DS.equals("")){
            kata = kata.substring(0,kata.length()-DS.length());
            if(dictionaryLookup(kata))
                return kata;
        }

        return kata;
    }

    /**
     * 
     * @param kata
     * @param partikel
     * @param kataGanti
     * @param DS
     * @return
     */
    private String loopPengembalianAkhiran(String kata, String partikel,
            String kataGanti, String DS)
    {
        String hasil = new String(kata);
        String log = hasil;
        
        //pertama, cek dulu
        if(dictionaryLookup(kata))
            return kata;
        else{
            log = loopReduksiAwalan(hasil,"",true);
            if(dictionaryLookup(log))
                return log;
        }
        
        //stelah itu, baru pengembalian satu-per-satu akhirannya
        if(!DS.equals("")){
            while(!DS.equals("")){
                if(DS.equals("kan")){
                    hasil = hasil + "k"; DS="an";
                }
                else{
                    hasil = hasil + DS; DS="";
                }
                
                if(dictionaryLookup(hasil))
                    return hasil;
                else{
                    log = loopReduksiAwalan(hasil,DS,true);
                    if(dictionaryLookup(log))
                        return log;
                }
            }
        }
        //lalu kata ganti kepunyaan
        if(!kataGanti.equals("")){
            hasil = hasil + kataGanti;
            kataGanti = "";
            
            if(dictionaryLookup(hasil))
                return hasil;
            else{
                log = loopReduksiAwalan(hasil,"",true);
                if(dictionaryLookup(log))
                    return log;
            }
        }
        //lalu terakhir, Partikel
        if(!partikel.equals("")){
            hasil = hasil + partikel;
            partikel = "";
            
            if(dictionaryLookup(hasil))
                return hasil;
            else{
                log = loopReduksiAwalan(hasil,"",true);
                if(dictionaryLookup(log))
                    return log;
            }
        }
        return hasil;
    }
    
    /**
     * Fungsi recoding(int rule, String kata)
     * berguna untuk melakukan rekonstruksi/penambahan karakter tertentu
     * sesuai rule-rule yg dinyatakan Jelita Asian 
     */
    public String recoding(int rule, String kata)
    {
        String recodingChar="", result=kata;
        switch(rule){
            case 1:  case 6: recodingChar = "r"; break;
            case 13: recodingChar = "p"; break;
            case 15: recodingChar = "t"; break;
            case 17: recodingChar = "k"; break;
            case 18: recodingChar = "s"; break;
            case 21: recodingChar = "r"; break;
            case 26: recodingChar = "p"; break;
            case 28: recodingChar = "t"; break;
            case 30: recodingChar = "k"; break;
            case 31: recodingChar = "s"; break;
            case 32: recodingChar = "p"; break;
        }
        if(!recodingChar.equals("")){
            if(rule==13 || rule==15 || rule==26 || rule==28){
                result = recodingChar + kata.substring(1,kata.length());
            }
            else{
                result = recodingChar + kata;
                //ini tambahan untuk algoritma Optimized CS Stemmer
                if(pilihanStemmer == ENHANCED_CS_STEMMER){
                    if(rule==17 || rule==30)
                    {
                        if(dictionaryLookup(result))
                            return result;
                        else
                            result = result.substring(2,result.length());
                    }
                }
            }
        }

        return result;
    }

    /**
     * Fungsi dictionaryLookup(String kata)
     * berguna untuk menguji apakah parameter kata ada pada database kamus
     * @param kata input string kata yang ingin dicari di database kamus. 
     */
    private boolean dictionaryLookup(String kata)
    {
        return kamus.isContain(kata);
    }
}
